﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NLite.Collections;
using NLite.Mini.Context;
using NLite.Reflection;
using NLite.Mini.Activation;
using System.Collections;

namespace NLite.Mini.Listener.Internal
{
    sealed class InjectService
    {
        public static object Get(string id, Type injectionType, IKernel locator, bool injectMany)
        {
            return injectMany
                ? InjectManyService.Get(injectionType, locator)
                : InjectOneService.Get(id, injectionType, locator);
        }
    }

    sealed class LazyService
    {
        class Methods
        {
            public static readonly MethodInfo LazyGet = typeof(ILazyServiceLocator).GetMethod("LazyGet");
            public static readonly MethodInfo LazyMetadataGet = typeof(ILazyServiceLocator).GetMethod("LazyMetadataGet");
            public static readonly MethodInfo LazyMetadataGetAll = typeof(ILazyServiceLocator).GetMethod("LazyMetadataGetAll");
            public static readonly MethodInfo LazyGetAll = typeof(ILazyServiceLocator).GetMethod("LazyGetAll");
        }

        public static object LazyGet(string id, Type lazyType, IKernel locator)
        {
            var elementType = lazyType.GetGenericArguments()[0];
            return Methods.LazyGet.MakeGenericMethod(elementType).FastFuncInvoke(locator, id);
        }

        public static object LazyMetadataGet(string id, Type lazyType, IKernel locator)
        {
            var elementType = lazyType.GetGenericArguments()[0];
            var metadataType = lazyType.GetGenericArguments()[1];

            return Methods.LazyMetadataGet.MakeGenericMethod(elementType, metadataType).FastFuncInvoke(locator, id);
        }

        public static object LazyMetadataGetAll(IKernel locator, Type collectionType, Type injectionType)
        {
            var result = LazyMetadataGetAll(locator, collectionType);

            return Mapper.Map(result, result.GetType(), injectionType);
        }

        public static IEnumerable LazyMetadataGetAll(IKernel locator, Type collectionType)
        {
            var result = (IEnumerable)Methods.LazyMetadataGetAll.MakeGenericMethod(collectionType.GetGenericArguments()).FastFuncInvoke(locator);
            return result;
        }

        public static object LazyGetAll(IKernel locator, Type collectionType, Type injectionType)
        {
            var result = LazyGetAll(locator, collectionType);
            return Mapper.Map(result, result.GetType(), injectionType);
        }

        public static IEnumerable LazyGetAll(IKernel locator, Type collectionType)
        {
            return (IEnumerable)Methods.LazyGetAll.MakeGenericMethod(collectionType.GetGenericArguments()).FastFuncInvoke(locator);
        }
    }

    static class InjectOneService
    {
        public static object Get(string id, Type injectionType, IKernel locator)
        {
            if (injectionType.IsGenericType)
            {
                var genericFieldTypeName = injectionType.GetGenericTypeDefinition().Name;
                if (genericFieldTypeName == "Lazy`1")
                    return LazyService.LazyGet(id, injectionType, locator);
                else if (genericFieldTypeName == "Lazy`2")
                    return LazyService.LazyMetadataGet(id, injectionType, locator);
            }

            if (!string.IsNullOrEmpty(id))
                return locator.Get(id);
            return locator.Get(injectionType);
        }
    }

    static class InjectManyService
    {
        sealed class InjectLazy<T> : Lazy<T>
        {
            public InjectLazy(Func<object> func)
                : base(ConvertStrongTypeFunc(func))
            {
            }

            private static Func<T> ConvertStrongTypeFunc(Func<object> func)
            {
                Func<T> result = null;
                result = () => (T)func();
                return result;
            }
        }



        public static object Get(Type injectionType, IKernel locator)
        {
            //如果被注入的组件集合是数组
            if (injectionType.IsArray)
                return GetArray(injectionType, locator);

            var genericFieldTypeName = injectionType.GetGenericTypeDefinition().Name;

            if (genericFieldTypeName == "Lazy`1")
                return MakeLazy(injectionType, locator);

            return GetAll(injectionType, locator);

        }

        private static object GetArray(Type injectionType, IKernel locator)
        {
            var arrayElementType = injectionType.GetElementType();

            if (arrayElementType.IsGenericType)
            {
                var genericName = arrayElementType.GetGenericTypeDefinition().Name;

                IEnumerable array = null;
                if (genericName == "Lazy`1") array = LazyService.LazyGetAll(locator, arrayElementType);
                if (genericName == "Lazy`2") array = LazyService.LazyMetadataGetAll(locator, arrayElementType);
                return Mapper.Map(array, Types.IEnumerableofT.MakeGenericType(arrayElementType), injectionType);
            }

            return Mapper.Map(locator.GetAll(arrayElementType), Types.IEnumerableofT.MakeGenericType(arrayElementType), arrayElementType.MakeArrayType());
        }

        static object MakeLazy(Type lazyElementType, IKernel locator)
        {
            var ft = lazyElementType.GetGenericArguments()[0];
            var lazyType = typeof(InjectLazy<>).MakeGenericType(ft);
            var ctor = lazyType.GetConstructor(new Type[] { typeof(Func<object>) });

            Func<object> func = () => Get(ft, locator);
            var lazy = ctor.FastInvoke(func);
            return lazy;
        }



        private static object GetAll(Type injectionType, IKernel locator)
        {
            var collectionElementType = injectionType.GetGenericArguments()[0];
            if (collectionElementType.IsGenericType)
            {
                var genericFieldTypeName = collectionElementType.GetGenericTypeDefinition().Name;

                if (genericFieldTypeName == "Lazy`1")
                    return LazyService.LazyGetAll(locator, collectionElementType, injectionType);

                if (genericFieldTypeName == "Lazy`2")
                    return LazyService.LazyMetadataGetAll(locator, collectionElementType, injectionType);
            }


            return Mapper.Map(
                locator.GetAll(collectionElementType)
                , Types.IEnumerableofT.MakeGenericType(collectionElementType)
                , injectionType);
        }


    }


    sealed class MemberMatcher
    {
        public static bool Match(ParameterInfo p, IKernel kernel)
        {
            var att = p.GetAttribute<InjectAttribute>(false);
            if (att != null)
            {
                if (!string.IsNullOrEmpty(att.Id))
                    return kernel.HasRegister(att.Id);
                return MemberMatcher.Match(p.ParameterType, kernel);
            }

            var manyAtt = p.GetAttribute<InjectManyAttribute>(false);
            if (manyAtt != null)
                return MemberMatcher.MatchMany(p.ParameterType, kernel);

            return kernel.HasRegister(p.ParameterType);
        }

        public static bool Match(Type memeberType, IKernel kernel)
        {
            if (memeberType.IsGenericType)
            {
                var genericFieldTypeName = memeberType.GetGenericTypeDefinition().Name;
                if (genericFieldTypeName == "Lazy`1" || genericFieldTypeName == "Lazy`2")
                {
                    var ft = memeberType.GetGenericArguments()[0];
                    return kernel.HasRegister(ft);
                }
            }
            return kernel.HasRegister(memeberType);
        }

        public static bool MatchMany(Type memeberType, IKernel kernel)
        {
            if (memeberType == Types.String)
                return false;

            if (memeberType.IsArray)
                return FilterArray(memeberType, kernel);

            var genericFieldTypeName = memeberType.GetGenericTypeDefinition().Name;
            if (genericFieldTypeName == "Lazy`1" /*|| genericFieldTypeName == "Lazy`2"*/ )
                return FilterLazyEnumerable(memeberType, kernel);

            return FilterEnumerableLazy(memeberType, kernel, ref genericFieldTypeName);
        }

        private static bool FilterArray(Type memeberType, IKernel kernel)
        {
            var arrayElementType = memeberType.GetElementType();

            if (arrayElementType.IsGenericType)
            {
                var genericName = arrayElementType.GetGenericTypeDefinition().Name;
                if (genericName == "Lazy`1" || genericName == "Lazy`2")
                    return kernel.HasRegister(arrayElementType.GetGenericArguments()[0]);
            }
            return kernel.HasRegister(arrayElementType);
        }

        //集合里面的元素都是Lazy元素--> Lazy<Component>[], IEnumerable<Lazy<Component>>
        private static bool FilterEnumerableLazy(Type memeberType, IKernel kernel, ref string genericFieldTypeName)
        {
            var collectionElementType = memeberType.GetGenericArguments()[0];
            if (collectionElementType.IsGenericType)
            {
                genericFieldTypeName = collectionElementType.GetGenericTypeDefinition().Name;
                if (genericFieldTypeName == "Lazy`1" || genericFieldTypeName == "Lazy`2")
                {
                    var ft = collectionElementType.GetGenericArguments()[0];
                    return kernel.HasRegister(ft);
                }
            }

            return kernel.HasRegister(memeberType.GetGenericArguments()[0]);
        }

        //Lazy元素的数据成员是集合--> Lazy<IEnumerable<Component>>
        private static bool FilterLazyEnumerable(Type lazyType, IKernel kernel)
        {
            var collectionType = lazyType.GetGenericArguments()[0];

            if (collectionType.IsArray)
                return kernel.HasRegister(collectionType.GetElementType());

            if (collectionType.IsGenericType && Types.IEnumerable.IsAssignableFrom(collectionType))
                return kernel.HasRegister(collectionType.GetGenericArguments()[0]);

            throw new InjectManyException();
        }
    }
}
